home *** CD-ROM | disk | FTP | other *** search
- /*
- * tclGlob.c --
- *
- * This file provides procedures and commands for file name
- * manipulation, such as tilde expansion and globbing.
- *
- * Copyright 1990-1991 Regents of the University of California
- * Permission to use, copy, modify, and distribute this
- * software and its documentation for any purpose and without
- * fee is hereby granted, provided that the above copyright
- * notice appear in all copies. The University of California
- * makes no representations about the suitability of this
- * software for any purpose. It is provided "as is" without
- * express or implied warranty.
- */
-
- #ifndef lint
- static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclGlob.c,v 1.24 92/07/01 08:51:50 ouster Exp $ SPRITE (Berkeley)";
- #endif /* not lint */
-
- #include "tclInt.h"
- #include "tclUnix.h"
-
- /*
- * The structure below is used to keep track of a globbing result
- * being built up (i.e. a partial list of file names). The list
- * grows dynamically to be as big as needed.
- */
-
- typedef struct {
- char *result; /* Pointer to result area. */
- int totalSpace; /* Total number of characters allocated
- * for result. */
- int spaceUsed; /* Number of characters currently in use
- * to hold the partial result (not including
- * the terminating NULL). */
- int dynamic; /* 0 means result is static space, 1 means
- * it's dynamic. */
- } GlobResult;
-
- /*
- * Declarations for procedures local to this file:
- */
-
- static void AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
- char *dir, char *separator, char *name,
- int nameLength));
- static int DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
- char *rem));
-
- /*
- *----------------------------------------------------------------------
- *
- * AppendResult --
- *
- * Given two parts of a file name (directory and element within
- * directory), concatenate the two together and append them to
- * the result building up in interp.
- *
- * Results:
- * There is no return value.
- *
- * Side effects:
- * Interp->result gets extended.
- *
- *----------------------------------------------------------------------
- */
-
- static void
- AppendResult(interp, dir, separator, name, nameLength)
- Tcl_Interp *interp; /* Interpreter whose result should be
- * appended to. */
- char *dir; /* Name of directory, without trailing
- * slash except for root directory. */
- char *separator; /* Separator string so use between dir and
- * name: either "/" or "" depending on dir. */
- char *name; /* Name of file withing directory (NOT
- * necessarily null-terminated!). */
- int nameLength; /* Number of characters in name. */
- {
- int dirFlags, nameFlags;
- char *p, saved;
-
- /*
- * Next, see if we can put together a valid list element from dir
- * and name by calling Tcl_AppendResult.
- */
-
- if (*dir == 0) {
- dirFlags = 0;
- } else {
- Tcl_ScanElement(dir, &dirFlags);
- }
- saved = name[nameLength];
- name[nameLength] = 0;
- Tcl_ScanElement(name, &nameFlags);
- if ((dirFlags == 0) && (nameFlags == 0)) {
- if (*interp->result != 0) {
- Tcl_AppendResult(interp, " ", dir, separator, name, (char *) NULL);
- } else {
- Tcl_AppendResult(interp, dir, separator, name, (char *) NULL);
- }
- name[nameLength] = saved;
- return;
- }
-
- /*
- * This name has weird characters in it, so we have to convert it to
- * a list element. To do that, we have to merge the characters
- * into a single name. To do that, malloc a buffer to hold everything.
- */
-
- p = (char *) ckalloc((unsigned) (strlen(dir) + strlen(separator)
- + nameLength + 1));
- sprintf(p, "%s%s%s", dir, separator, name);
- name[nameLength] = saved;
- Tcl_AppendElement(interp, p, 0);
- ckfree(p);
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * DoGlob --
- *
- * This recursive procedure forms the heart of the globbing
- * code. It performs a depth-first traversal of the tree
- * given by the path name to be globbed.
- *
- * Results:
- * The return value is a standard Tcl result indicating whether
- * an error occurred in globbing. After a normal return the
- * result in interp will be set to hold all of the file names
- * given by the dir and rem arguments. After an error the
- * result in interp will hold an error message.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- static int
- DoGlob(interp, dir, rem)
- Tcl_Interp *interp; /* Interpreter to use for error
- * reporting (e.g. unmatched brace). */
- char *dir; /* Name of a directory at which to
- * start glob expansion. This name
- * is fixed: it doesn't contain any
- * globbing chars. */
- char *rem; /* Path to glob-expand. */
- {
- /*
- * When this procedure is entered, the name to be globbed may
- * already have been partly expanded by ancestor invocations of
- * DoGlob. The part that's already been expanded is in "dir"
- * (this may initially be empty), and the part still to expand
- * is in "rem". This procedure expands "rem" one level, making
- * recursive calls to itself if there's still more stuff left
- * in the remainder.
- */
-
- register char *p;
- register char c;
- char *openBrace, *closeBrace;
- int gotSpecial, result;
- char *separator;
-
- /*
- * Figure out whether we'll need to add a slash between the directory
- * name and file names within the directory when concatenating them
- * together.
- */
-
- if ((dir[0] == 0) || ((dir[0] == '/') && (dir[1] == 0))) {
- separator = "";
- } else {
- separator = "/";
- }
-
- /*
- * When generating information for the next lower call,
- * use static areas if the name is short, and malloc if the name
- * is longer.
- */
-
- #define STATIC_SIZE 200
-
- /*
- * First, find the end of the next element in rem, checking
- * along the way for special globbing characters.
- */
-
- gotSpecial = 0;
- openBrace = closeBrace = NULL;
- for (p = rem; ; p++) {
- c = *p;
- if ((c == '\0') || (c == '/')) {
- break;
- }
- if ((c == '{') && (openBrace == NULL)) {
- openBrace = p;
- }
- if ((c == '}') && (closeBrace == NULL)) {
- closeBrace = p;
- }
- if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
- gotSpecial = 1;
- }
- }
-
- /*
- * If there is an open brace in the argument, then make a recursive
- * call for each element between the braces. In this case, the
- * recursive call to DoGlob uses the same "dir" that we got.
- * If there are several brace-pairs in a single name, we just handle
- * one here, and the others will be handled in recursive calls.
- */
-
- if (openBrace != NULL) {
- int remLength, l1, l2;
- char static1[STATIC_SIZE];
- char *element, *newRem;
-
- if (closeBrace == NULL) {
- Tcl_ResetResult(interp);
- interp->result = "unmatched open-brace in file name";
- return TCL_ERROR;
- }
- remLength = strlen(rem) + 1;
- if (remLength <= STATIC_SIZE) {
- newRem = static1;
- } else {
- newRem = (char *) ckalloc((unsigned) remLength);
- }
- l1 = openBrace-rem;
- strncpy(newRem, rem, l1);
- p = openBrace;
- for (p = openBrace; *p != '}'; ) {
- element = p+1;
- for (p = element; ((*p != '}') && (*p != ',')); p++) {
- /* Empty loop body: just find end of this element. */
- }
- l2 = p - element;
- strncpy(newRem+l1, element, l2);
- strcpy(newRem+l1+l2, closeBrace+1);
- if (DoGlob(interp, dir, newRem) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (remLength > STATIC_SIZE) {
- ckfree(newRem);
- }
- return TCL_OK;
- }
-
- /*
- * If there were any pattern-matching characters, then scan through
- * the directory to find all the matching names.
- */
-
- if (gotSpecial) {
- DIR *d;
- struct dirent *entryPtr;
- int l1, l2;
- char *pattern, *newDir, *dirName;
- char static1[STATIC_SIZE], static2[STATIC_SIZE];
- struct stat statBuf;
-
- /*
- * Be careful not to do any actual file system operations on a
- * directory named ""; instead, use ".". This is needed because
- * some versions of UNIX don't treat "" like "." automatically.
- */
-
- if (*dir == '\0') {
- dirName = ".";
- } else {
- dirName = dir;
- }
- if ((stat(dirName, &statBuf) != 0)
- || ((statBuf.st_mode & S_IFMT) != S_IFDIR)) {
- return TCL_OK;
- }
- d = opendir(dirName);
- if (d == NULL) {
- Tcl_ResetResult(interp);
- Tcl_AppendResult(interp, "couldn't read directory \"",
- dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
- return TCL_ERROR;
- }
- l1 = strlen(dir);
- l2 = (p - rem);
- if (l2 < STATIC_SIZE) {
- pattern = static2;
- } else {
- pattern = (char *) ckalloc((unsigned) (l2+1));
- }
- strncpy(pattern, rem, l2);
- pattern[l2] = '\0';
- result = TCL_OK;
- while (1) {
- entryPtr = readdir(d);
- if (entryPtr == NULL) {
- break;
- }
-
- /*
- * Don't match names starting with "." unless the "." is
- * present in the pattern.
- */
-
- if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
- continue;
- }
- if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
- int nameLength = strlen(entryPtr->d_name);
- if (*p == 0) {
- AppendResult(interp, dir, separator, entryPtr->d_name,
- nameLength);
- } else {
- if ((l1+nameLength+2) <= STATIC_SIZE) {
- newDir = static1;
- } else {
- newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
- }
- sprintf(newDir, "%s%s%s", dir, separator, entryPtr->d_name);
- result = DoGlob(interp, newDir, p+1);
- if (newDir != static1) {
- ckfree(newDir);
- }
- if (result != TCL_OK) {
- break;
- }
- }
- }
- }
- closedir(d);
- if (pattern != static2) {
- ckfree(pattern);
- }
- return result;
- }
-
- /*
- * This is the simplest case: just another path element. Move
- * it to the dir side and recurse (or just add the name to the
- * list, if we're at the end of the path).
- */
-
- if (*p == 0) {
- AppendResult(interp, dir, separator, rem, p-rem);
- } else {
- int l1, l2;
- char *newDir;
- char static1[STATIC_SIZE];
-
- l1 = strlen(dir);
- l2 = l1 + (p - rem) + 2;
- if (l2 <= STATIC_SIZE) {
- newDir = static1;
- } else {
- newDir = (char *) ckalloc((unsigned) l2);
- }
- sprintf(newDir, "%s%s%.*s", dir, separator, p-rem, rem);
- result = DoGlob(interp, newDir, p+1);
- if (newDir != static1) {
- ckfree(newDir);
- }
- if (result != TCL_OK) {
- return TCL_ERROR;
- }
- }
- return TCL_OK;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_TildeSubst --
- *
- * Given a name starting with a tilde, produce a name where
- * the tilde and following characters have been replaced by
- * the home directory location for the named user.
- *
- * Results:
- * The result is a pointer to a static string containing
- * the new name. This name will only persist until the next
- * call to Tcl_TildeSubst; save it if you care about it for
- * the long term. If there was an error in processing the
- * tilde, then an error message is left in interp->result
- * and the return value is NULL.
- *
- * Side effects:
- * None that the caller needs to worry about.
- *
- *----------------------------------------------------------------------
- */
-
- char *
- Tcl_TildeSubst(interp, name)
- Tcl_Interp *interp; /* Interpreter in which to store error
- * message (if necessary). */
- char *name; /* File name, which may begin with "~/"
- * (to indicate current user's home directory)
- * or "~<user>/" (to indicate any user's
- * home directory). */
- {
- #define STATIC_BUF_SIZE 50
- static char staticBuf[STATIC_BUF_SIZE];
- static int curSize = STATIC_BUF_SIZE;
- static char *curBuf = staticBuf;
- char *dir;
- int length;
- int fromPw = 0;
- register char *p;
-
- if (name[0] != '~') {
- return name;
- }
-
- /*
- * First, find the directory name corresponding to the tilde entry.
- */
-
- if ((name[1] == '/') || (name[1] == '\0')) {
- dir = getenv("HOME");
- if (dir == NULL) {
- Tcl_ResetResult(interp);
- Tcl_AppendResult(interp, "couldn't find HOME environment ",
- "variable to expand \"", name, "\"", (char *) NULL);
- return NULL;
- }
- p = name+1;
- } else {
- struct passwd *pwPtr;
-
- for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
- /* Null body; just find end of name. */
- }
- length = p-&name[1];
- if (length >= curSize) {
- length = curSize-1;
- }
- memcpy((VOID *) curBuf, (VOID *) (name+1), length);
- curBuf[length] = '\0';
- pwPtr = getpwnam(curBuf);
- if (pwPtr == NULL) {
- Tcl_ResetResult(interp);
- Tcl_AppendResult(interp, "user \"", curBuf,
- "\" doesn't exist", (char *) NULL);
- return NULL;
- }
- dir = pwPtr->pw_dir;
- fromPw = 1;
- }
-
- /*
- * Grow the buffer if necessary to make enough space for the
- * full file name.
- */
-
- length = strlen(dir) + strlen(p);
- if (length >= curSize) {
- if (curBuf != staticBuf) {
- ckfree(curBuf);
- }
- curSize = length + 1;
- curBuf = (char *) ckalloc((unsigned) curSize);
- }
-
- /*
- * Finally, concatenate the directory name with the remainder
- * of the path in the buffer.
- */
-
- strcpy(curBuf, dir);
- strcat(curBuf, p);
- if (fromPw) {
- endpwent();
- }
- return curBuf;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_GlobCmd --
- *
- * This procedure is invoked to process the "glob" Tcl command.
- * See the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation.
- *
- *----------------------------------------------------------------------
- */
-
- /* ARGSUSED */
- int
- Tcl_GlobCmd(dummy, interp, argc, argv)
- ClientData dummy; /* Not used. */
- Tcl_Interp *interp; /* Current interpreter. */
- int argc; /* Number of arguments. */
- char **argv; /* Argument strings. */
- {
- int i, result, noComplain;
-
- if (argc < 2) {
- notEnoughArgs:
- Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
- " ?-nocomplain? name ?name ...?\"", (char *) NULL);
- return TCL_ERROR;
- }
- noComplain = 0;
- if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
- if (argc < 3) {
- goto notEnoughArgs;
- }
- noComplain = 1;
- }
-
- for (i = 1 + noComplain; i < argc; i++) {
- char *thisName;
-
- /*
- * Do special checks for names starting at the root and for
- * names beginning with ~. Then let DoGlob do the rest.
- */
-
- thisName = argv[i];
- if (*thisName == '~') {
- thisName = Tcl_TildeSubst(interp, thisName);
- if (thisName == NULL) {
- return TCL_ERROR;
- }
- }
- if (*thisName == '/') {
- result = DoGlob(interp, "/", thisName+1);
- } else {
- result = DoGlob(interp, "", thisName);
- }
- if (result != TCL_OK) {
- return result;
- }
- }
- if ((*interp->result == 0) && !noComplain) {
- char *sep = "";
-
- Tcl_AppendResult(interp, "no files matched glob pattern",
- (argc == 2) ? " \"" : "s \"", (char *) NULL);
- for (i = 1; i < argc; i++) {
- Tcl_AppendResult(interp, sep, argv[i], (char *) NULL);
- sep = " ";
- }
- Tcl_AppendResult(interp, "\"", (char *) NULL);
- return TCL_ERROR;
- }
- return TCL_OK;
- }
-